import 'dart:io';

import 'package:path/path.dart' as path;
import 'package:serverpod_cli/src/analyzer/dart/definitions.dart';
import 'package:serverpod_cli/src/analyzer/dart/endpoints_analyzer.dart';
import 'package:serverpod_cli/src/generator/code_generation_collector.dart';
import 'package:serverpod_serialization/serverpod_serialization.dart';
import 'package:test/test.dart';

import '../../../../test_util/endpoint_validation_helpers.dart';

const pathToServerpodRoot = '../../../../../../../..';
var testProjectDirectory = Directory(
  path.joinAll([
    'test',
    'integration',
    'analyzer',
    'dart',
    'endpoint_validation',
    const Uuid().v4(),
  ]),
);

void main() {
  setUpAll(() async {
    await createTestEnvironment(testProjectDirectory, pathToServerpodRoot);
  });

  tearDownAll(() {
    testProjectDirectory.deleteSync(recursive: true);
  });

  group('Given a valid endpoint class when analyzed', () {
    var collector = CodeGenerationCollector();
    var testDirectory = Directory(
      path.join(testProjectDirectory.path, const Uuid().v4()),
    );

    late List<EndpointDefinition> endpointDefinitions;
    late EndpointsAnalyzer analyzer;
    setUpAll(() async {
      var endpointFile = File(path.join(testDirectory.path, 'endpoint.dart'));
      endpointFile.createSync(recursive: true);
      endpointFile.writeAsStringSync('''
import 'package:serverpod/serverpod.dart';

class ExampleEndpoint extends Endpoint {
  Future<String> hello(Session session, String name) async {
    return 'Hello \$name';
  }
}
''');
      analyzer = EndpointsAnalyzer(testDirectory);
      endpointDefinitions = await analyzer.analyze(collector: collector);
    });

    test('then no validation errors are reported.', () {
      expect(collector.errors, isEmpty);
    });

    test('then endpoint definition is created.', () {
      expect(endpointDefinitions, hasLength(1));
    });

    group('then endpoint definition', () {
      test('has expected name.', () {
        var name = endpointDefinitions.firstOrNull?.name;
        expect(name, 'example');
      });

      test('has no documentation.', () {
        var documentation =
            endpointDefinitions.firstOrNull?.documentationComment;
        expect(documentation, isNull);
      });

      test('has expected class name.', () {
        var className = endpointDefinitions.firstOrNull?.className;
        expect(className, 'ExampleEndpoint');
      });

      test('has an endpoint method defined.', () {
        var methods = endpointDefinitions.firstOrNull?.methods;
        expect(methods, hasLength(1));
      });

      test('has expected filePath.', () {
        var filePath = endpointDefinitions.firstOrNull?.filePath;
        expect(
          filePath,
          path.join(
            Directory.current.path,
            testDirectory.path,
            'endpoint.dart',
          ),
        );
      });
    });
  });

  group('Given a valid endpoint with documentation when analyzed', () {
    var collector = CodeGenerationCollector();
    var testDirectory = Directory(
      path.join(testProjectDirectory.path, const Uuid().v4()),
    );

    late List<EndpointDefinition> endpointDefinitions;
    late EndpointsAnalyzer analyzer;
    setUpAll(() async {
      var endpointFile = File(path.join(testDirectory.path, 'endpoint.dart'));
      endpointFile.createSync(recursive: true);
      endpointFile.writeAsStringSync('''
import 'package:serverpod/serverpod.dart';

/// This is an example endpoint.
class ExampleEndpoint extends Endpoint {
  Future<String> hello(Session session, String name) async {
    return 'Hello \$name';
  }
}
''');
      analyzer = EndpointsAnalyzer(testDirectory);
      endpointDefinitions = await analyzer.analyze(collector: collector);
    });

    test('then no validation errors are reported.', () {
      expect(collector.errors, isEmpty);
    });

    test('then endpoint definition is created.', () {
      expect(endpointDefinitions, hasLength(1));
    });

    test('then endpoint definition has expected documentation.', () {
      var documentation = endpointDefinitions.firstOrNull?.documentationComment;
      expect(documentation, '/// This is an example endpoint.');
    });
  });

  group(
    'Given a dart class that does not inherit from Endpoint when analyzed',
    () {
      var collector = CodeGenerationCollector();
      var testDirectory = Directory(
        path.join(testProjectDirectory.path, const Uuid().v4()),
      );

      late List<EndpointDefinition> endpointDefinitions;
      late EndpointsAnalyzer analyzer;
      setUpAll(() async {
        var endpointFile = File(path.join(testDirectory.path, 'endpoint.dart'));
        endpointFile.createSync(recursive: true);
        endpointFile.writeAsStringSync('''
import 'package:serverpod/serverpod.dart';

class ExampleEndpoint {
  Future<String> hello(Session session, String name) async {
    return 'Hello, \$name!';
  }
}
''');
        analyzer = EndpointsAnalyzer(testDirectory);
        endpointDefinitions = await analyzer.analyze(collector: collector);
      });

      test('then no validation errors are reported.', () {
        expect(collector.errors, isEmpty);
      });

      test('then no endpoint definition are created.', () {
        expect(endpointDefinitions, isEmpty);
      });
    },
  );

  group('Given same endpoint class definition in multiple files when analyzed', () {
    var collector = CodeGenerationCollector();
    var testDirectory = Directory(
      path.join(testProjectDirectory.path, const Uuid().v4()),
    );

    late List<EndpointDefinition> endpointDefinitions;
    late EndpointsAnalyzer analyzer;
    setUpAll(() async {
      var firstEndpointFile = File(
        path.join(testDirectory.path, 'endpoint.dart'),
      );
      firstEndpointFile.createSync(recursive: true);
      firstEndpointFile.writeAsStringSync('''
import 'package:serverpod/serverpod.dart';

class ExampleEndpoint extends Endpoint {
  Future<String> hello(Session session, String name) async {
    return 'Hello \$name';
  }
}
''');
      var secondEndpointFile = File(
        path.join(testDirectory.path, 'endpoint2.dart'),
      );
      secondEndpointFile.createSync(recursive: true);
      secondEndpointFile.writeAsStringSync('''
import 'package:serverpod/serverpod.dart';

class ExampleEndpoint extends Endpoint {
  Future<String> hello(Session session, String name) async {
    return 'Hello \$name';
  }
}
''');
      analyzer = EndpointsAnalyzer(testDirectory);
      endpointDefinitions = await analyzer.analyze(collector: collector);
    });

    test('then two validation errors are reported.', () {
      expect(collector.errors, hasLength(2));
    });

    test(
      'then validation error explains that multiple endpoint definitions exist.',
      () {
        expect(
          collector.errors.firstOrNull?.message,
          'Multiple endpoint definitions for ExampleEndpoint exists. Please provide a unique name for each endpoint class.',
        );
      },
    );

    test('then no endpoint definition are created.', () {
      expect(endpointDefinitions, isEmpty);
    });
  });
}
